package simulation;

import java.awt.event.KeyEvent;
import java.util.Collection;

import util.Location;
import view.Canvas;


/**
 * handles all user interaction with the Model.
 * @author Schwartz & Roy
 *
 */
public class UserInteraction {

    // simulation data
    private Model myModel;
    private Canvas myView;
    private Environment myEnvironment;

    // user created masses or springs
    private Spring myUserSpring;
    private Mass myUserMass;
    private double USER_SPRING_KVAL = 0.45;

    // special keys
    private static final int PAUSE_KEY = KeyEvent.VK_P;
    private static final int CLEAR_KEY = KeyEvent.VK_C;
    private static final int ADD_SIMULATION_KEY = KeyEvent.VK_N;
    private static final int GRAVITY_SWITCH = KeyEvent.VK_G;
    private static final int VISCOSITY_SWITCH = KeyEvent.VK_V;
    private static final int CENTERMASS_SWITCH = KeyEvent.VK_M;
    private static final int TOPWALL_SWITCH = KeyEvent.VK_1;
    private static final int RIGHTWALL_SWITCH = KeyEvent.VK_2;
    private static final int BOTTOMWALL_SWITCH = KeyEvent.VK_3;
    private static final int LEFTWALL_SWITCH = KeyEvent.VK_4;
    private static final int INCREASE_SIZE = KeyEvent.VK_UP;
    private static final int DECREASE_SIZE = KeyEvent.VK_DOWN;

    // value for the change in the bounds when
    //         increasing or decreasing.
    private static final int BOUNDS_MODIFIER = 10;

    /**
     * constructor sets up user interaction.
     * @param model is the Model for the simulation
     * @param view is the Canvas for the simulation
     * @param envir is the Environment for the simulation
     */
    public UserInteraction(Model model, Canvas view, Environment envir) {
        myModel = model;
        myView = view;
        myEnvironment = envir;
    }

    /**
     * Takes in recent user commands since the last update
     *      and applies them to the simulation.
     */
    public void applyUserInteraction() {
        applyUserKeyboardInteraction();
        applyUserMouseInteraction();
    }

    /**
     * handles user interaction with the keyboard.
     */
    private void applyUserKeyboardInteraction() {
        // Simulation changes
        Collection<Integer> myKeysReleased = myView.getKeysReleased();
        if (myKeysReleased.contains(PAUSE_KEY)) {
            myModel.changePauseState();
        }
        if (myKeysReleased.contains(CLEAR_KEY)) {
            myModel.clearAssemblies();
        }
        if (myKeysReleased.contains(ADD_SIMULATION_KEY)) {
            myView.loadSimulation();
        }
        // Environmental changes
        if (myKeysReleased.contains(GRAVITY_SWITCH)) {
            myEnvironment.setForceToggle(Environment.GRAVITY_NAME);
        }
        if (myKeysReleased.contains(VISCOSITY_SWITCH)) {
            myEnvironment.setForceToggle(Environment.VISCOSITY_NAME);
        }
        if (myKeysReleased.contains(CENTERMASS_SWITCH)) {
            myEnvironment.setForceToggle(Environment.CENTERMASS_NAME);
        }
        if (myKeysReleased.contains(TOPWALL_SWITCH)) {
            myEnvironment.setForceToggle(Environment.TOPWALL_NAME);
        }
        if (myKeysReleased.contains(RIGHTWALL_SWITCH)) {
            myEnvironment.setForceToggle(Environment.RIGHTWALL_NAME);
        }
        if (myKeysReleased.contains(BOTTOMWALL_SWITCH)) {
            myEnvironment.setForceToggle(Environment.BOTTOMWALL_NAME);
        }
        if (myKeysReleased.contains(LEFTWALL_SWITCH)) {
            myEnvironment.setForceToggle(Environment.LEFTWALL_NAME);
        }

        // Wall bounds changes
        if (myKeysReleased.contains(INCREASE_SIZE)) {
            myModel.modifyBounds(BOUNDS_MODIFIER, BOUNDS_MODIFIER);
        }
        if (myKeysReleased.contains(DECREASE_SIZE)) {
            myModel.modifyBounds(-BOUNDS_MODIFIER, -BOUNDS_MODIFIER);
        }
    }

    /**
     * interprets mouse actions from the user.
     */
    private void applyUserMouseInteraction() {
        // User mouse input
        if (myView.getLastMousePosition() != null) {
            Location mouse = new Location(myView.getLastMousePosition().x,
                    myView.getLastMousePosition().y);
            if (myUserSpring != null) {
                myUserMass.setCenter(mouse);
            } else {
                createUserControlledSpringAndMass(mouse);
            }
        } else if (myUserSpring != null) {
            deleteUserSpringAndMass();
        }
    }

    /**
     * Creates a mass at mouse position and a spring attached
     *      to it and the nearest mass.
     * @param mouse is the location of the mouse pointer
     */
    private void createUserControlledSpringAndMass(Location mouse) {
        if (myModel.getMasses().isEmpty()) {
            return;
        }
        myUserMass = new FixedMass(mouse.x, mouse.y, 0);
        double minDistance = Double.MAX_VALUE;
        Mass closestMass = myUserMass;
        for (Mass m : myModel.getMasses()) {
            if (myUserMass.distance(m) < minDistance)   {
                minDistance = myUserMass.distance(m);
                closestMass = m;
            }
        }
        myUserSpring = new Spring(myUserMass, closestMass,
                minDistance, USER_SPRING_KVAL);
        myModel.add(myUserSpring);
    }

    /**
     * Deletes the user controlled objects.
     */
    private void deleteUserSpringAndMass() {
        myModel.remove(myUserSpring);
        myUserSpring = null;
        myUserMass = null;
    }

}
